package de.unikoblenz.west.csxpoi;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.impl.client.DefaultHttpClient;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;

import de.unikoblenz.west.csxpoi.R;

//import de.unikoblenz.west.csxpoi.PoiEditorActivity.AsyncSave;

import android.app.Activity;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.View;
import android.view.Window;
import android.webkit.WebView;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.AutoCompleteTextView;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;

/**
 * The activity for editing the ontology.
 */
public class OntologyEditorActivity extends Activity {

	/**
	 * the constant representing the synchronizing dialog
	 */
	private static final int SYNCHRONIZING_DIALOG = 1;

	/**
	 * the category to edit
	 */
	private CategoryWrapper mCategory = null;

	/**
	 * the list of relations of the category
	 */
	private List<RelationWrapper> mRelations = new ArrayList<RelationWrapper>();

	/**
	 * temporary list of relations marked for deletion
	 */
	private List<RelationWrapper> mDeletedRel = new ArrayList<RelationWrapper>();

	/**
	 * the ID of the editing user
	 */
	private String mUserId = null;

	/**
	 * Called when the activity is started.
	 */
	@Override
	protected void onCreate(Bundle savedInstanceState) {

		super.onCreate(savedInstanceState);

		requestWindowFeature(Window.FEATURE_NO_TITLE);

		Bundle extras = getIntent().getExtras();
		if (extras != null) {

			mCategory = new CategoryWrapper();
			mCategory.setUri(extras.getString("uri"));
			mCategory.setName(extras.getString("name"));
			mUserId = extras.getString("user");

			if (mCategory != null && mCategory.getName() != null) {
				initializeView();
			} else {

				chooseCategory();
			}
		} else {

			chooseCategory();
		}
	}

	/**
	 * Called when the activity is brought back to the front.
	 */
	@Override
	protected void onResume() {
		super.onResume();
		updateView();
	}

	/**
	 * Displays a list of categories to choose from.
	 */
	private void chooseCategory() {

		// Categories:
		setContentView(R.layout.ontology_creator);

		ArrayAdapter<CategoryWrapper> categoriesAdapter = new ArrayAdapter<CategoryWrapper>(
				this, android.R.layout.simple_dropdown_item_1line,
				OntologyBuffer.getCategories());
		final AutoCompleteTextView newCategoryView = ((AutoCompleteTextView) findViewById(R.id.new_category));

		// Edit button:
		final Button editButton = (Button) findViewById(R.id.edit_button);
		editButton.setOnClickListener(new View.OnClickListener() {
			public void onClick(View view) {
				mCategory = (CategoryWrapper) OntologyBuffer
						.getNamedCategory(newCategoryView.getText());

				System.out.println("Kategorie: " + mCategory.getName());

				initializeView();
			}
		});

		// Cancel button:
		final Button cancelButton = (Button) findViewById(R.id.cancel_button);
		cancelButton.setOnClickListener(new View.OnClickListener() {
			public void onClick(View v) {
				finish();
			}
		});

		newCategoryView.setAdapter(categoriesAdapter);
	}

	/**
	 * Initializes the constant parts of the view.
	 */
	private void initializeView() {

		setContentView(R.layout.ontology_editor);

		// Predicates:

		Spinner predicateSpinner = (Spinner) findViewById(R.id.predicates);
		ArrayAdapter<PropertyWrapper> predicatesAdapter = new ArrayAdapter<PropertyWrapper>(
				this, R.layout.spinner_item,
				PropertyWrapper.CONTINGENT_PROPERTIES);
		predicatesAdapter
				.setDropDownViewResource(R.layout.spinner_dropdown_item);
		predicateSpinner.setAdapter(predicatesAdapter);

		ArrayAdapter<CategoryWrapper> objectsAdapter = new ArrayAdapter<CategoryWrapper>(
				this, android.R.layout.simple_dropdown_item_1line,
				OntologyBuffer.getCategories(mCategory));
		final AutoCompleteTextView objectView = ((AutoCompleteTextView) findViewById(R.id.object_category));
		objectView.setAdapter(objectsAdapter);

		// Cancel button:

		final Button cancelButton = (Button) findViewById(R.id.cancel_button);
		cancelButton.setOnClickListener(new View.OnClickListener() {
			public void onClick(View view) {
				finish();
			}
		});

		// Delete button:

		final Button deleteButton = (Button) findViewById(R.id.delete_button);
		if (mUserId != null) {
			deleteButton.setOnClickListener(new View.OnClickListener() {
				public void onClick(View view) {
					new AsyncDelete().execute();
				}
			});
		} else {
			deleteButton.setEnabled(false);
		}

		// Save button:

		final Button saveButton = (Button) findViewById(R.id.save_button);
		if (mUserId != null) {
			saveButton.setOnClickListener(new View.OnClickListener() {
				public void onClick(View view) {
					new AsyncSave().execute();
				}
			});
		} else {
			saveButton.setEnabled(false);
		}

		// Plus button:

		final ImageButton plusButton = (ImageButton) findViewById(R.id.plus_button);
		plusButton.setOnClickListener(new View.OnClickListener() {
			public void onClick(View view) {
				Spinner predicates = (Spinner) findViewById(R.id.predicates);
				PropertyWrapper predicate = (PropertyWrapper) predicates
						.getSelectedItem();

				CategoryWrapper object = (CategoryWrapper) OntologyBuffer
						.getNamedCategory(objectView.getText());
				RelationWrapper relation = new RelationWrapper(predicate,
						object);
				if (!mRelations.contains(relation))
					mRelations.add(relation);
				updateView();
			}
		});

		// Name:

		final EditText nameView = (EditText) findViewById(R.id.name);
		nameView.setText(mCategory.getName());

		if (mCategory.getUri() != null) {
			new AsyncRelationsLoad().execute();
		}

	}

	/**
	 * Updates the variable parts of the view.
	 */
	private void updateView() {

		final LinearLayout relationItemsContainer = (LinearLayout) findViewById(R.id.relation_items_container);
		final LinearLayout deletedItemsContainer = (LinearLayout) findViewById(R.id.deleted_items_container);

		if (relationItemsContainer != null) {

			relationItemsContainer.removeAllViews();

			for (final RelationWrapper relation : mRelations) {

				if (!relation.isRemoved()) {

					// Relation:

					final LinearLayout relationView = (LinearLayout) View
							.inflate(this, R.layout.relation_item, null);
					final TextView predicateView = (TextView) relationView
							.findViewById(R.id.predicate);

					predicateView.setText(relation.getPredicate().getName());
					predicateView
							.setOnClickListener(new View.OnClickListener() {
								public void onClick(View view) {
									dereference(relation.getPredicate());
								}
							});
					final TextView objectView = (TextView) relationView
							.findViewById(R.id.object);
					objectView.setText(Helpers.getUnderlinedString(relation
							.getObject().getName()));
					objectView.setOnClickListener(new View.OnClickListener() {
						public void onClick(View view) {
							dereference(relation.getObject());
						}
					});
					relationItemsContainer.addView(relationView);

					// Minus button:

					final ImageButton minusButton = (ImageButton) relationView
							.findViewById(R.id.minus_button);
					minusButton.setOnClickListener(new View.OnClickListener() {
						public void onClick(View view) {
							RelationWrapper relation = null;
							for (int i = 0; i < mRelations.size(); i++) {
								relation = mRelations.get(i);
								if (relation
										.getPredicate()
										.getName()
										.equals(predicateView.getText()
												.toString())
										&& relation
												.getObject()
												.getName()
												.equals(objectView.getText()
														.toString())) {
									mDeletedRel.add(relation);
									if (relation.isImported())
										relation.setRemoved(true);
									else
										mRelations.remove(i);

									System.out
											.println("Length of mDeletedRel: "
													+ mDeletedRel.size());
								}
							}
							updateView();
						}
					});

					relationItemsContainer.invalidate();
				}
			}
		}

		if (deletedItemsContainer != null) {
			deletedItemsContainer.removeAllViews();
			for (final RelationWrapper relation : mDeletedRel) {

				// Relation:

				final LinearLayout deletionView = (LinearLayout) View.inflate(
						this, R.layout.deleted_item, null);
				final TextView predicateView = (TextView) deletionView
						.findViewById(R.id.predicate);

				predicateView.setText(relation.getPredicate().getName());
				predicateView.setOnClickListener(new View.OnClickListener() {
					public void onClick(View view) {
						dereference(relation.getPredicate());
					}
				});
				final TextView objectView = (TextView) deletionView
						.findViewById(R.id.object);
				objectView.setText(Helpers.getUnderlinedString(relation
						.getObject().getName()));
				objectView.setOnClickListener(new View.OnClickListener() {
					public void onClick(View view) {
						dereference(relation.getObject());
					}
				});
				deletedItemsContainer.addView(deletionView);

				// Plus button:

				final ImageButton plusButton = (ImageButton) deletionView
						.findViewById(R.id.plus_button);
				plusButton.setOnClickListener(new View.OnClickListener() {
					public void onClick(View view) {
						RelationWrapper relation = null;
						for (int i = 0; i < mDeletedRel.size(); i++) {
							relation = mDeletedRel.get(i);

							if (relation.getPredicate().getName()
									.equals(predicateView.getText().toString())
									&& relation
											.getObject()
											.getName()
											.equals(objectView.getText()
													.toString())) {

								mDeletedRel.remove(i);
								if (relation.isImported()) {
									relation.setRemoved(false);
									for (int j = 0; j < mRelations.size(); j++) {
										if (mRelations.get(j) == relation)
											mRelations.set(j, relation);
									}

								} else
									mRelations.add(relation);
							}
						}
						updateView();
					}
				});

				deletedItemsContainer.invalidate();
			}
		}

	}

	/**
	 * Called when a dialog is shown.
	 */
	@Override
	protected Dialog onCreateDialog(int id) {
		switch (id) {
		case SYNCHRONIZING_DIALOG:
			ProgressDialog waitAPIDialog = new ProgressDialog(this);
			waitAPIDialog.setTitle(R.string.synchronizing_dialog_title);
			waitAPIDialog.setMessage(getResources().getString(
					R.string.please_wait_text));
			waitAPIDialog.setIndeterminate(true);
			waitAPIDialog.setCancelable(false);
			return waitAPIDialog;
		}
		return super.onCreateDialog(id);
	}

	/**
	 * Dereferences the specified property by opening a browser for its URI.
	 * 
	 * @param property
	 *            the property to dereference
	 */
	private void dereference(PropertyWrapper property) {
		openBrowser(property.getUri());
	}

	/**
	 * Dereferences the specified category by opening a browser (external
	 * resources) for its URI or editing the category (internal resources).
	 * 
	 * @param category
	 *            the category to dereference
	 */
	private void dereference(CategoryWrapper category) {
		if (category.getUri().startsWith(Constants.NS_VOC)) {
			mCategory = category;
			initializeView();
			updateView();
		} else {
			openBrowser(category.getUri());
		}
	}

	/**
	 * Opens a browser for the specified URL.
	 * 
	 * @param url
	 *            the URL for which to open a browser
	 */
	private void openBrowser(String url) {
		WebView webView = new WebView(this);
		webView.loadUrl(url);
	}

	/**
	 * Asynchronously loads the relations of the category.
	 */
	private class AsyncRelationsLoad extends AsyncTask<Void, Void, Boolean> {

		/**
		 * Called before the asynchronous loading of relations.
		 */
		@Override
		protected void onPreExecute() {
			showDialog(SYNCHRONIZING_DIALOG);
		}

		/**
		 * Called when starting the asynchronous loading of relations.
		 */
		@Override
		protected Boolean doInBackground(Void... args) {

			boolean success = false;

			try {
				URL url = new URL(Constants.REST_ADDRESS + "categories/"
						+ Encoder.urlEncode(mCategory.getUri()));
				SAXParserFactory spf = SAXParserFactory.newInstance();
				SAXParser sp = spf.newSAXParser();
				XMLReader xr = sp.getXMLReader();
				RelationsSaxHandler relationsSaxHandler = new RelationsSaxHandler();
				xr.setContentHandler(relationsSaxHandler);
				xr.parse(new InputSource(url.openStream()));
				List<RelationWrapper> list = relationsSaxHandler.getList();
				if (list != null) {
					mRelations = list;
					success = true;
				}
			} catch (UnsupportedEncodingException e) {
				success = false;
			} catch (MalformedURLException e) {
				success = false;
			} catch (ParserConfigurationException e) {
				success = false;
			} catch (SAXException e) {
				success = false;
			} catch (IOException e) {
				success = false;
			}

			return success;
		}

		/**
		 * Called after the asynchronous loading of relations.
		 */
		@Override
		protected void onPostExecute(Boolean success) {
			dismissDialog(SYNCHRONIZING_DIALOG);
			if (!success) {
				Toast.makeText(OntologyEditorActivity.this,
						R.string.server_error_message, Toast.LENGTH_LONG)
						.show();
				finish();
			} else {
				updateView();
			}
		}
	}

	/**
	 * Parses the XML response from the category REST request.
	 */
	private class RelationsSaxHandler extends DefaultHandler {

		/**
		 * the list of relations of the category
		 */
		private List<RelationWrapper> mList = new ArrayList<RelationWrapper>();

		/**
		 * the current category
		 */
		private CategoryWrapper mCurrentCategory = null;

		/**
		 * the characters of the current tag
		 */
		private String mCurrentCharacters = "";

		/**
		 * the constant for the name tag
		 */
		private static final String NAME_TAG = "name";

		/**
		 * the constant for the subclass tag
		 */
		private static final String SUBCLASS_TAG = "subclass";

		/**
		 * the constant for the superclass tag
		 */
		private static final String SUPERCLASS_TAG = "superclass";

		/**
		 * the constant for the equivalent tag
		 */
		private static final String EQUIVALENT_TAG = "equivalent";

		/**
		 * the constant for the id attribute
		 */
		private static final String ID_ATTRIBUTE = "id";

		/**
		 * Handles opening tags.
		 */
		@Override
		public void startElement(String uri, String localName, String qName,
				Attributes attributes) throws SAXException {
			if (localName.equals(SUBCLASS_TAG)
					|| localName.equals(SUPERCLASS_TAG)
					|| localName.equals(EQUIVALENT_TAG)) {
				mCurrentCategory = new CategoryWrapper();
				mCurrentCategory.setUri(attributes.getValue(ID_ATTRIBUTE));
			}
		}

		/**
		 * Handles closing tags.
		 */
		@Override
		public void endElement(String uri, String localName, String qName)
				throws SAXException {
			if (mCurrentCategory != null) {
				if (localName.equals(NAME_TAG)) {
					mCurrentCategory.setName(mCurrentCharacters);
				} else if (localName.equals(SUBCLASS_TAG)) {
					mList.add(new RelationWrapper(
							PropertyWrapper.SUPERCLASS_OF, mCurrentCategory,
							true));
					mCurrentCategory = null;
				} else if (localName.equals(SUPERCLASS_TAG)) {
					mList.add(new RelationWrapper(PropertyWrapper.SUBCLASS_OF,
							mCurrentCategory, true));
					mCurrentCategory = null;
				} else if (localName.equals(EQUIVALENT_TAG)) {
					mList.add(new RelationWrapper(
							PropertyWrapper.EQUIVALENT_TO, mCurrentCategory,
							true));
					mCurrentCategory = null;
				}
			}
		}

		/**
		 * Handles characters of tags.
		 */
		@Override
		public void characters(char[] ch, int start, int length)
				throws SAXException {
			mCurrentCharacters = new String(ch, start, length);
		}

		/**
		 * Gets the list of relations of the category
		 * 
		 * @return the list of relations of the category
		 */
		public List<RelationWrapper> getList() {
			return mList;
		}
	}

	/**
	 * Asynchronously saves the category.
	 */
	private class AsyncSave extends AsyncTask<Void, Void, Boolean> {

		/**
		 * Called before the asynchonous saving of the category.
		 */
		@Override
		protected void onPreExecute() {
			showDialog(SYNCHRONIZING_DIALOG);
		}

		/**
		 * Called when starting the asynchronous saving of the category.
		 */
		@Override
		protected Boolean doInBackground(Void... args) {

			boolean success = true;

			final EditText resourceView = (EditText) findViewById(R.id.name);
			String name = resourceView.getText().toString();

			try {
				HttpClient client = new DefaultHttpClient();
				if (mCategory.getUri() != null) { // modify existing category
					System.out.println("Save changed category...");

					if (!name.equals(mCategory.getName())) {
						mCategory.setName(name);
						String nameUrlString = Constants.REST_ADDRESS
								+ "categories/"
								+ Encoder.urlEncode(mCategory.getUri())
								+ "?user=" + Encoder.urlEncode(mUserId)
								+ "&changes="
								+ Encoder.urlEncode(Constants.URI_RDFS_LABEL)
								+ "&value=" + Encoder.urlEncode(name);
						HttpPut nameRequest = new HttpPut(nameUrlString);
						HttpResponse nameResponse = client.execute(nameRequest);
						if (nameResponse != null
								&& nameResponse.getStatusLine().getStatusCode() != 404) {

							success = true;
						} else {
							success = false;
						}
					}
					for (RelationWrapper relation : mRelations) {
						String relationUrlString = relation
								.generateModificationUrlString(
										mCategory.getUri(), mUserId);
						if (relationUrlString != null) {
							HttpPut relationRequest = new HttpPut(
									relationUrlString);
							HttpResponse relationResponse = client
									.execute(relationRequest);
							if (relationResponse != null
									&& relationResponse.getStatusLine()
											.getStatusCode() != 404) {

								success &= true;
							}
						}
					}
				} else { // create new category

					System.out.println("Creating new category " + name);

					mCategory.setName(name);
					String urlString = Constants.REST_ADDRESS + "categories"
							+ "?user=" + Encoder.urlEncode(mUserId) + "&name="
							+ Encoder.urlEncode(mCategory.getName());
					int i = 1;
					for (RelationWrapper relation : mRelations) {
						if (relation.getPredicate().equals(
								PropertyWrapper.SUBCLASS_OF)) {
							urlString += "&superclass"
									+ i
									+ "="
									+ Encoder.urlEncode(relation.getObject()
											.getUri());
							i++;
						}
					}
					i = 1;
					for (RelationWrapper relation : mRelations) {
						if (relation.getPredicate().equals(
								PropertyWrapper.SUPERCLASS_OF)) {
							urlString += "&subclass"
									+ i
									+ "="
									+ Encoder.urlEncode(relation.getObject()
											.getUri());
							i++;
						}
					}
					i = 1;
					for (RelationWrapper relation : mRelations) {
						if (relation.getPredicate().equals(
								PropertyWrapper.EQUIVALENT_TO)) {
							urlString += "&equivalent"
									+ i
									+ "="
									+ Encoder.urlEncode(relation.getObject()
											.getUri());
							i++;
						}
					}

					System.out.println(urlString);

					HttpPost request = new HttpPost(urlString);
					HttpResponse response = client.execute(request);
					if (response != null
							&& response.getStatusLine().getStatusCode() != 404) {

						// TODO: get URI from response
						mCategory.setUri(Constants.NS_VOC
								+ URLEncoder.encode(mCategory.getName(),
										"UTF-8"));
						success = true;
					}
				}
			} catch (IOException e) {
				e.printStackTrace();
			}

			success &= OntologyBuffer.updateCategories();

			return success;
		}

		/**
		 * Called after the asynchronous saving of the category.
		 */
		@Override
		protected void onPostExecute(Boolean success) {
			dismissDialog(SYNCHRONIZING_DIALOG);
			returnCategory(success);
		}
	}

	/**
	 * Asynchronously deletes the category.
	 */
	private class AsyncDelete extends AsyncTask<Void, Void, Boolean> {

		/**
		 * Called before the asynchronous deletion of the category.
		 */
		@Override
		protected void onPreExecute() {
			showDialog(SYNCHRONIZING_DIALOG);
		}

		/**
		 * Called when the asynchronous deletion of the category is started.
		 */
		@Override
		protected Boolean doInBackground(Void... args) {

			boolean success = false;

			try {
				HttpClient client = new DefaultHttpClient();
				String urlString = Constants.REST_ADDRESS + "categories/"
						+ Encoder.urlEncode(mCategory.getUri()) + "?user="
						+ Encoder.urlEncode(mUserId);
				HttpDelete request = new HttpDelete(urlString);
				HttpResponse response = client.execute(request);
				if (response != null
						&& response.getStatusLine().getStatusCode() != 404) {

					success = true;
				} else {
					success = false;
				}
			} catch (IOException e) {
				e.printStackTrace();
			}

			success &= OntologyBuffer.updateCategories();

			return success;
		}

		/**
		 * Called after the asynchronous deletion of the category.
		 */
		@Override
		protected void onPostExecute(Boolean success) {
			dismissDialog(SYNCHRONIZING_DIALOG);
			returnCategory(success);
		}
	}

	/**
	 * Returns the category to the requesting activity.
	 * 
	 * @param success
	 *            true if the category was edited successfully.
	 */
	private void returnCategory(boolean success) {
		Toast.makeText(
				OntologyEditorActivity.this,
				success ? R.string.ontology_saved_message
						: R.string.ontology_error_message, Toast.LENGTH_LONG)
				.show();
		Intent resultIntent = new Intent(OntologyEditorActivity.this,
				PoiEditorActivity.class);
		resultIntent.putExtra("category",
				mCategory.getUri() != null ? mCategory : null);
		setResult(RESULT_OK, resultIntent);
		finish();
	}

}
